"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EntityGenerator = void 0;
const fs_extra_1 = require("fs-extra");
const core_1 = require("@mikro-orm/core");
const knex_1 = require("@mikro-orm/knex");
const SourceFile_1 = require("./SourceFile");
const EntitySchemaSourceFile_1 = require("./EntitySchemaSourceFile");
class EntityGenerator {
    constructor(em) {
        this.em = em;
        this.config = this.em.config;
        this.driver = this.em.getDriver();
        this.platform = this.driver.getPlatform();
        this.helper = this.platform.getSchemaHelper();
        this.connection = this.driver.getConnection();
        this.namingStrategy = this.config.getNamingStrategy();
        this.sources = [];
    }
    static register(orm) {
        orm.config.registerExtension('@mikro-orm/entity-generator', new EntityGenerator(orm.em));
    }
    async generate(options = {}) {
        const baseDir = core_1.Utils.normalizePath(options.baseDir ?? (this.config.get('baseDir') + '/generated-entities'));
        const schema = await knex_1.DatabaseSchema.create(this.connection, this.platform, this.config);
        let metadata = schema.getTables()
            .filter(table => !options.schema || table.schema === options.schema)
            .sort((a, b) => a.name.localeCompare(b.name))
            .map(table => {
            const skipColumns = options.skipColumns?.[table.getShortestName()];
            if (skipColumns) {
                table.getColumns().forEach(col => {
                    if (skipColumns.includes(col.name)) {
                        table.removeColumn(col.name);
                    }
                });
            }
            return table.getEntityDeclaration(this.namingStrategy, this.helper);
        });
        for (const meta of metadata) {
            for (const prop of meta.relations) {
                if (options.skipTables?.includes(prop.referencedTableName)) {
                    prop.reference = core_1.ReferenceType.SCALAR;
                    const meta2 = metadata.find(m => m.className === prop.type);
                    prop.type = meta2.getPrimaryProps().map(pk => pk.type).join(' | ');
                }
            }
        }
        metadata = metadata.filter(table => !options.skipTables || !options.skipTables.includes(table.tableName));
        this.detectManyToManyRelations(metadata);
        if (this.config.get('entityGenerator').bidirectionalRelations) {
            this.generateBidirectionalRelations(metadata);
        }
        if (this.config.get('entityGenerator').identifiedReferences) {
            this.generateIdentifiedReferences(metadata);
        }
        const esmImport = this.config.get('entityGenerator').esmImport ?? false;
        for (const meta of metadata) {
           // if (!meta.pivotTable) {
                if (this.config.get('entityGenerator').entitySchema) {
                    this.sources.push(new EntitySchemaSourceFile_1.EntitySchemaSourceFile(meta, this.namingStrategy, this.platform, esmImport));
                }
                else {
                    this.sources.push(new SourceFile_1.SourceFile(meta, this.namingStrategy, this.platform, esmImport));
                }
           // }
        }
        if (options.save) {
            this.sources.forEach(file => file.generate());
            await (0, fs_extra_1.ensureDir)(baseDir);
           await Promise.all(this.sources.map(file => (0, fs_extra_1.writeFile)(baseDir + '/' + file.getBaseName(), file.generate())));
        }
        return this.sources.map(file => file.generate());
    }
    detectManyToManyRelations(metadata) {
        for (const meta of metadata) {
            if (meta.compositePK && // needs to have composite PK
                meta.primaryKeys.length >= meta.relations.length && // all relations are PKs
                meta.relations.length === 2 && // there are exactly two relation properties
                // meta.relations.length === meta.props.length && // all properties are relations
                meta.relations.every(prop => prop.reference === core_1.ReferenceType.MANY_TO_ONE) // all relations are m:1
            ) {
                meta.pivotTable = true;
                const owner = metadata.find(m => m.className === meta.relations[0].type);
                if (!owner) {
                    continue;
                }
                const name = this.namingStrategy.columnNameToProperty(meta.tableName.replace(new RegExp('^' + owner.tableName + '_'), ''));
                owner.addProperty({
                    name,
                    reference: core_1.ReferenceType.MANY_TO_MANY,
                    pivotTable: meta.tableName,
                    type: meta.relations[1].type,
                    joinColumns: meta.relations[0].fieldNames,
                    inverseJoinColumns: meta.relations[1].fieldNames,
                });
            }
        }
    }
    generateBidirectionalRelations(metadata) {
        for (const meta of metadata.filter(m => !m.pivotTable)) {
            for (const prop of meta.relations) {
                const targetMeta = metadata.find(m => m.className === prop.type);
                const newProp = {
                    name: meta.className.match(/([A-Z][a-z]+)+?/g).pop().toLowerCase(),
                    type: meta.className,
                    joinColumns: prop.fieldNames,
                    referencedTableName: meta.tableName,
                    referencedColumnNames: core_1.Utils.flatten(targetMeta.getPrimaryProps().map(pk => pk.fieldNames)),
                    mappedBy: prop.name,
                };

                if (prop.reference === core_1.ReferenceType.MANY_TO_ONE) {
                    newProp.reference = core_1.ReferenceType.ONE_TO_MANY;
                    
                    const count = meta.relations.reduce((count,r)=> r.type == prop.type?++count:count,0)

                    if(count > 1)
                        newProp.name = meta.className.match(/([A-Z][a-z]+)+?/g).pop().toLowerCase() + 'As' + prop.name.substr(0,1).toUpperCase() + prop.name.substr(1);
                }
                else if (prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.mappedBy) {
                    newProp.reference = core_1.ReferenceType.ONE_TO_ONE;
                    newProp.nullable = true;
                }
                else if (prop.reference === core_1.ReferenceType.MANY_TO_MANY && !prop.mappedBy) {
                    newProp.reference = core_1.ReferenceType.MANY_TO_MANY;
                    newProp.name = meta.className.match(/([A-Z][a-z]+)+?/g).pop().toLowerCase() + 'As' + prop.name.substr(0,1).toUpperCase() + prop.name.substr(1);
                }
                else {
                    continue;
                }
                targetMeta.addProperty(newProp);
            }
        }
    }
    generateIdentifiedReferences(metadata) {
        for (const meta of metadata.filter(m => !m.pivotTable)) {
            for (const prop of meta.relations) {
                if ([core_1.ReferenceType.MANY_TO_ONE, core_1.ReferenceType.ONE_TO_ONE].includes(prop.reference)) {
                    prop.wrappedReference = true;
                }
            }
        }
    }
}
exports.EntityGenerator = EntityGenerator;
